/*
  Copyright 2006 by Sean Luke and George Mason University
  Licensed under the Academic Free License version 3.0
  See the file "LICENSE" for more information
*/

package sim.engine;
import java.lang.reflect.*;
import sim.util.*;

/** 
    A Steppable which calls an underlying method using Java's reflection system.  The underlying method can either have no arguments
    or have one argumen (SimState), and is specified by its name as a String.

    <p>You can use MethodStep to call methods on classes that aren't Steppables.  For example, if you have an object <b>myObject</b>
    and wish to submit both its <b>foo</b> and <b>bar</b> methods (no arguments) to be called at various times when the Schedule
    sees fit, you might do this:

    <pre><tt>
    MyClass myObject = ... ;
    schedule.scheduleRepeating( ... , new MethodStep(myObject, "foo"), ...);
    schedule.scheduleRepeating( ... , new MethodStep(myObject, "bar"), ...);
    </tt></pre>

    <p>This will call the <b>foo()</b> method and <b>bar()</b> method to be called at the appropriate times on myObject. 

    <p>You can also use MethodStep to pop up objects which aren't Steppables by their nature.  For example:

    <pre><tt>
    JFrame myJFrame = ... ;
    schedule.schedule( ... , new MethodStep(myJFrame, "show"), ...);
    </tt></pre>

    <p>MethodStep can also be called on methods which expect to be passed in the Steppable's <b>SimState</b> argument.  When
    the MethodStep is called, it passes its argument to them.  You do this by adding a <b>true</b> to the constructor:

    <pre><tt>
    MyClass myObject = ... ;
    schedule.scheduleRepeating( ... , new MethodStep(myObject, "baz", true), ...);
    schedule.scheduleRepeating( ... , new MethodStep(myObject, "quux", true), ...);
    </tt></pre>

    <p>This will call the <b>baz(SimState)</b> method and <b>quux(SimState)</b> method to be called at the appropriate times on myObject.

    <p>This method is mostly to help in ease of porting: but it's not good Java.  Reflection is slow and violates all sorts of design
    contracts -- generally speaking, you should use anonymous Steppables intead.  For example, the examples above could have instead been
    written this way:

    <pre><tt>
    final MyClass myObject = ... ;
    final JFrame myJFrame = ... ;
    schedule.scheduleRepeating( ... , new Steppable() { public void step(SimState state) { myObject.foo() } }, ...);
    schedule.scheduleRepeating( ... , new Steppable() { public void step(SimState state) { myObject.foo() } }, ...);
    schedule.schedule( ... , new Steppable() { public void step(SimState state) { myJFrame.show() } }, ...);
    schedule.scheduleRepeating( ... , new Steppable() { public void step(SimState state) { myObject.baz(state) } }, ...);
    schedule.scheduleRepeating( ... , new Steppable() { public void step(SimState state) { myObject.quux(state) } }, ...);
    </tt></pre>
*/

public class MethodStep implements Steppable
    {
    Method method;
    Object object;
    boolean passInSimState;
        
    public MethodStep(Object object, String methodName)
        {
        this(object,methodName,false);
        }
                        
    public MethodStep(Object object, String methodName, boolean passInSimState)
        {
        this.object = object;
        this.passInSimState = passInSimState;
        if (object==null)
            {
            throw new NullPointerException("MethodStep asked to call the method " + method + (passInSimState ? "\"(SimState state)" : "\"") + " on a null object");
            }
        try
            {
            if (passInSimState)
                this.method = object.getClass().getMethod(methodName, new Class[]{SimState.class});
            else
                this.method = object.getClass().getMethod(methodName, new Class[]{});
            }
        catch (NoSuchMethodException ex)  // make runtime exception
            {
            throw new RuntimeException("Could not find a public method called \"" + method + (passInSimState ? "\"(SimState state)" : "\"") + " in the class " + object.getClass());
            }
        catch (SecurityException ex)
            {
            throw new RuntimeException("Could not find a public method called \"" + method + (passInSimState ? "\"(SimState state)" : "\"") + " in the class " + object.getClass());
            }
        }
                
    public void step(final SimState state)
        {
        try
            {
            if (passInSimState) method.invoke(object, new Object[] { state });
            else method.invoke(object, (Object[]) null);
            }
        catch (IllegalAccessException ex)  // make runtime exception
            {
            // generally should not happen -- the getMethod() method should only return public methods
            throw new RuntimeException("Could not find a public method called \"" + method + (passInSimState ? "\"(SimState state)" : "\"") + " in the class " + object.getClass());
            }
        catch (IllegalArgumentException ex)
            {
            throw new RuntimeException("Could not find a public method called \"" + method + (passInSimState ? "\"(SimState state)" : "\"") + " in the class " + object.getClass());
            }
        catch (InvocationTargetException ex)
            {
            /** At the moment, Java 1.3 cannot do causes, so we don't include this for compatability reasons.  Maybe we'll uncomment it later. */
            /*
              Throwable t = new RuntimeException("On calling \"" + methodName +"\" in the class " + object.getClass() + 
              ", an Exception was raised in the called method.");
              t.initCause(ex.getCause());
              throw t;
            */
            throw new RuntimeException("On calling \"" + method + (passInSimState ? "\"(SimState state)" : "\"") + " in the class " + object.getClass() + 
                ", an Exception was raised in the called method.", ex);
            }
        catch (NullPointerException ex)
            {
            // should not be able to occur -- we've already verified that object is not null.
            throw new NullPointerException("MethodStep asked to call the method " + method + (passInSimState ? "\"(SimState state)" : "\"") + " on a null object");
            }
        // dont' catch (ExceptionInInitializerError ex)  -- let it throw through like any error
        }
    }
